package com.qf.business.redenvlopes.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ken.event.action.apply.producer.EventTemplate;
import com.qf.business.redenvlopes.dao.RedEnvlopesDao;
import com.qf.business.redenvlopes.service.RedEnvlopesDescService;
import com.qf.business.redenvlopes.service.RedEnvlopesService;
import com.qf.business.user.feign.StuFeign;
import com.qf.commons.base.result.Codes;
import com.qf.commons.web.exception.ServiceException;
import com.qf.data.redenvlopes.entity.RedEnvlopes;
import com.qf.data.redenvlopes.entity.RedEnvlopesDesc;
import com.qf.data.redenvlopes.vo.RedSendVo;
import com.qf.data.user.entity.ScoreDesc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 红包表(RedEnvlopes)表服务实现类 - Redis版本
 *
 * @author makejava
 * @since 2022-12-03 14:42:13
 */
@Service("redEnvlopesService2")
@Slf4j
@Primary
public class RedEnvlopesServiceRedisImpl extends ServiceImpl<RedEnvlopesDao, RedEnvlopes> implements RedEnvlopesService {


    @Autowired
    private StuFeign stuFeign;

    @Autowired
    private RedEnvlopesDescService redEnvlopesDescService;


    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 发送红包的lua脚本
     */
    private String sendRedLua = "--发送红包的lua脚本 4个参数    KEYS -【红包id】ARGV -【总积分 份数 类型】\n" +
            "--红包id\n" +
            "local redid = KEYS[1]\n" +
            "--总积分\n" +
            "local score = ARGV[1]\n" +
            "--份数\n" +
            "local count = ARGV[2]\n" +
            "--类型\n" +
            "local type = ARGV[3]\n" +
            "\n" +
            "--生成红包的key\n" +
            "local key = 'red-envlopse-'..redid\n" +
            "\n" +
            "--判断当前的红包是否存在\n" +
            "local result = redis.call('exists', key) \n" +
            "if result == 1 then\n" +
            "   --说明红包记录在redis中存在\n" +
            "   return -1\n" +
            "end\n" +
            "\n" +
            "--红包记录不存在，保存红包记录\n" +
            "redis.call('hset', key, 'hasScore', score)\n" +
            "redis.call('hset', key, 'hasCount', count)\n" +
            "redis.call('hset', key, 'type', type)\n" +
            "return 1";


    private String redBackLua = "--红包过期的lua脚本\n" +
            "local redid = KEYS[1]\n" +
            "--判断红包是否存在\n" +
            "local key = 'red-envlopse-'..redid\n" +
            "local result = redis.call('exists', key)\n" +
            "if result == 0 then\n" +
            "   --红包信息不存在\n" +
            "   return -1\n" +
            "end\n" +
            "\n" +
            "--判断红包是否已经抢完\n" +
            "--获取redis中红包剩余的积分数\n" +
            "local hasScore = redis.call('hget', key, 'hasScore')\n" +
            "if hasScore == 0 then\n" +
            "    --红包已经抢完了\n" +
            "    redis.call('del', key)\n" +
            "    return -2\n" +
            "end\n" +
            "\n" +
            "--红包未抢完\n" +
            "redis.call('del', key)\n" +
            "return tonumber(hasScore)";

    /**
     * 发送红包业务
     * @param redSendVo
     * @return
     */
    @Override
    @Transactional
    public long sendRed(RedSendVo redSendVo) {

        //1、修改用户积分数据(扣减积分)
        Integer result = (Integer) stuFeign.updateUserScore(redSendVo.getScore(), 1).getData();

        if (result <= 0) {
            log.debug("[send red] 用户当前积分不足！");
            throw new ServiceException(Codes.SCORE_BUZU_ERROR);
        }

        //4、生成红包记录 - 获得红包id
        RedEnvlopes redEnvlopes = new RedEnvlopes()
                .setSid(redSendVo.getSid())
                .setType(redSendVo.getType())
                .setScore(redSendVo.getScore())
                .setCount(redSendVo.getCount())
                .setInfo(redSendVo.getInfo())
                .setTimeout(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));
        this.save(redEnvlopes);

        //生成积分流水
        ScoreDesc scoreDesc = new ScoreDesc()
                .setSid(redSendVo.getSid())
                .setScore(redSendVo.getScore())
                .setAction(1)
                .setType(0)
                .setBusinessId(redEnvlopes.getId());
        stuFeign.insertScoreDesc(scoreDesc);

        //操作redis 生成红包的相关数据
        Long luaResult = redisTemplate.execute(new DefaultRedisScript<>(sendRedLua, Long.class),
                Collections.singletonList(redEnvlopes.getId() + ""),
                redEnvlopes.getScore() + "",
                redEnvlopes.getCount() + "",
                redEnvlopes.getType() + "");

        //红包信息已经存在了
        if (luaResult == -1) {
            throw new ServiceException(Codes.RED_INFO_ERROR);
        }

        //将红包id发给延迟队列
        EventTemplate.sendDelay("red-back", redEnvlopes.getId(), 10, TimeUnit.SECONDS);

        //5、返回红包id
        return redEnvlopes.getId();
    }

    /**
     * 抢红包的业务
     * @param redid
     * @return
     */
    @Override
//    @GetUser
    @Transactional
//    @CLock("'rob-red-lock-' + #redid")
    public long robRed(Long redid) {

        //1、获取抢红包者的信息
//        Student student = UserUtils.getUser(Student.class);
//        Integer uid = student.getId();

        Long uid = (long)(Math.random() * 1000000);

        //select 查询语句带锁
        //2、通过红包id查询红包的详情信息 2800
        RedEnvlopes redEnvlopes = this.getBaseMapper().queryByIdLock(redid);

        //3、判断是否可抢 1-红包id无效 2-红包已经抢完 3-红包已经过期 4-当前自己已经抢过
        if (redEnvlopes == null) {
            //红包信息未查询到
//            throw new ServiceException(Codes.RED_INFO_ERROR);
            return -1;
        }

        if (redEnvlopes.getStatus() == 1) {
            //红包已经抢完
//            throw new ServiceException(Codes.RED_ROB_OVER);
            return -2;
        }

        if (redEnvlopes.getStatus() == 2) {
            //红包已经过期
//            throw new ServiceException(Codes.RED_ROB_TIMEOUT);
            return -3;
        }

        //判断是否已经抢过该红包
        Long robcount = redEnvlopesDescService.query().eq("rid", redid).eq("uid", uid).count();
        if (robcount > 0) {
            //说明已经抢过了
            throw new ServiceException(Codes.RED_ROB_CHONGFU);
        }


        //4、获取红包的各项信息
        Integer hasScore = redEnvlopes.getScore(); //红包中的剩余积分
        Integer count = redEnvlopes.getCount(); //红包的剩余的份数
        Integer type = redEnvlopes.getType(); //抢红包的类型

        //5、计算抢红包的积分
        Integer robScore = 0;//抢到的积分
        if(count == 1){
            //说明只剩下最后一个红包，剩余的积分都是当前用户的
            robScore = hasScore;
        } else {
            //不是最后一个红包
            if (type == 0) {
                //固定红包
                robScore = hasScore / count;
            } else {
                //随机红包 - 二倍均值法
                robScore = (int)(Math.random() * ((hasScore / count) * 2 - 1)) + 1;
            }
        }

        //6、修改红包记录 同时生成红包明细
        this.updateById((RedEnvlopes) redEnvlopes
                .setScore(hasScore - robScore) //8W+
                .setCount(--count)
                .setStatus(count == 0 ? 1 : 0)); //2790

        RedEnvlopesDesc redEnvlopesDesc = new RedEnvlopesDesc()
                .setRid(redEnvlopes.getId())
                .setUid(uid)
                .setScore(robScore)
                .setType(0);
        redEnvlopesDescService.save(redEnvlopesDesc);

        //7、修改当前抢到红包的用户的积分值
//        stuFeign.updateUserScore(robScore, 0);
        //生成用户积分流水
        ScoreDesc scoreDesc = new ScoreDesc()
                .setSid(uid)
                .setScore(robScore)
                .setAction(0)
                .setType(1)
                .setBusinessId(redEnvlopes.getId());
        stuFeign.insertScoreDesc(scoreDesc);

        return robScore;
    }

    /**
     * redis版本 抢红包的处理业务 - 通过事件总线触发
     * @param redid
     * @param uid
     * @param robScore
     * @return
     */
    @Override
    public long robRed(Long redid, Long uid, Long robScore) {
        RedEnvlopesDesc redEnvlopesDesc = new RedEnvlopesDesc()
                .setRid(redid)
                .setUid(uid)
                .setScore(robScore.intValue())
                .setType(0);
        redEnvlopesDescService.save(redEnvlopesDesc);
        return 1;
    }

    /**
     * 红包过期 - 根据红包id 设置当前红包过期
     * @param redid
     */
    @Override
    @Transactional
    public void redBack(Long redid) {
        //执行lua脚本
        Long result = redisTemplate.execute(new DefaultRedisScript<>(redBackLua, Long.class), Collections.singletonList(redid + ""));

        if (result == -1) {
            log.error("[red back error] 红包回退时有误，未找到该红包的信息 - {}", redid);
            return;
        }

        if (result > 0) {
            log.debug("[red back info] 红包回退时有剩余积分 - {}", result);

            //获取红包信息
            RedEnvlopes redEnvlopes = this.getById(redid);

            //设置红包的明细
            RedEnvlopesDesc redEnvlopesDesc = new RedEnvlopesDesc()
                    .setRid(redid)
                    .setUid(redEnvlopes.getSid())
                    .setScore(result.intValue())
                    .setType(1);
            redEnvlopesDescService.save(redEnvlopesDesc);

            //远程调用用户端，修改用户的积分
            stuFeign.updateUserScore(result.intValue(), redEnvlopes.getSid(), 0);

            //生成积分流水
            ScoreDesc scoreDesc = new ScoreDesc()
                    .setSid(redEnvlopes.getSid())
                    .setScore(result.intValue())
                    .setAction(0)
                    .setType(2)
                    .setBusinessId(redEnvlopes.getId());
            stuFeign.insertScoreDesc(scoreDesc);
        }
    }

    public static void main(String[] args) {
        //二倍均值法 
        // 1000 - 10
        // 1 ~ (x / y) * 2
        // 1 - 1 ~ 200 （100）
        // 2 - 1 ~ 200 (100)
        
        Integer score = 100000;
        Integer count = 20;

        Integer hasScore = score;
        Integer hasCount = count;

        Integer x = 0;

        for (Integer i = 0; i < count; i++) {

            Integer robScore;
            if (hasCount == 1) {
                robScore = hasScore;
            } else {
                //第 i 个抢红包的算法
                robScore = (int)(1 + Math.random() * ((hasScore / hasCount) * 2 - 1));
            }
            System.out.println("第" + i + "个用户抢到的积分：" + robScore);

            x += robScore;
            //修改剩余的数量
            hasScore -= robScore;
            hasCount--;
        }

        System.out.println("累加的结果：" + x);
    }
}

